package com.polito.mad.bikeponyapp.service;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;

import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.widget.Toast;

import com.polito.mad.bikeponyapp.R;
import com.polito.mad.bikeponyapp.activity.MainActivity;
import com.polito.mad.bikeponyapp.activity.NewTasksActivity;
import com.polito.mad.bikeponyapp.location.LocationObservable;
import com.polito.mad.bikeponyapp.location.LocationObserver;
import com.polito.mad.bikeponyapp.persistence.DeliveriesRepository;
import com.polito.mad.bikeponyapp.persistence.DeliveryItem;
import com.polito.mad.bikeponyapp.persistence.DeliveryItem.DeliveryItemType;
import com.polito.mad.bikeponyapp.persistence.RepositoryHolder;
import com.polito.mad.bikeponyapp.util.AgentState;
import com.polito.mad.bikeponyapp.util.ResultMessage;
import com.polito.mad.bikeponyapp.util.ServerUpdateAsyncTask;
import com.polito.mad.bikeponyapp.util.ServerUpdateDataContainer;

public class ServerCommunicationService extends Service implements LocationListener,LocationObservable{
	
	private NotificationManager mNM;
	
	
	//Unique identification Number for the Notification.
	// we use it on Notification dtart, and to cancel it
	private int NOTIFICATION = R.string.local_service_started;
	private ProximityIntentReceiver proximityIntentReceiver;
	private OnProximityListener onProximityListener;
	private PendingIntent proximityIntent;
	/**
	 * Class for clients to access. because we know this service always 
	 * runs in the same process as its clients, we don't need to deal with
	 * @author nicola
	 *
	 */
	public class LocalBinder extends Binder
	{
		public ServerCommunicationService getService()
		{
			return ServerCommunicationService.this;
		}
	}
	
	@Override
	public void onCreate() {
		super.onCreate();
		mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
		locationObservers = new ArrayList<LocationObserver>();
		locationProviders = Arrays.asList(new String[]{LocationManager.GPS_PROVIDER,LocationManager.NETWORK_PROVIDER,MY_TEST_PROVIDER});
		try {
			locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
			
			if(locationManager.getProvider(MY_TEST_PROVIDER)==null)
				locationManager.addTestProvider(MY_TEST_PROVIDER, false, false, false, false, false,false, false, android.location.Criteria.POWER_HIGH, android.location.Criteria.ACCURACY_FINE);
			locationManager.setTestProviderEnabled(MY_TEST_PROVIDER, true);
			
			mockLocationRunnable = new Runnable() {
				@Override
				public void run() {
					InputStream fileInputStream = ServerCommunicationService.this.getResources().openRawResource(R.raw.mock_location);
					Scanner scanner = new Scanner(fileInputStream);
					scanner.useLocale(Locale.ITALY);
					while(applicationState)
					{	
						if(!scanner.hasNext())
						{
							try {
								fileInputStream.close();
								fileInputStream = ServerCommunicationService.this.getResources().openRawResource(R.raw.mock_location);
								scanner = new Scanner(fileInputStream);
								
							} catch (IOException e) {
								e.printStackTrace();
							}
							
						}else 
						{
							double latitude=0;
							double longitude=0;
							if(scanner.hasNextDouble())
								latitude=scanner.nextDouble();
							else
								continue;
							if(scanner.hasNextDouble())
								longitude = scanner.nextDouble();
							else
								continue;
							setMockLocation(latitude,longitude);
						}
						try 
						{
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
							scanner.close();
							return;
						}
					}
					scanner.close();
				}
			};
			
			/*
			 * Create the repository
			 * */
			deliveryItemRepository= ((RepositoryHolder)getApplication()).getDeliveriesRepository();
			proximityIntentReceiver = new ProximityIntentReceiver();
			IntentFilter filter = new IntentFilter(getString(R.string.intent_action_proximity));
			registerReceiver(proximityIntentReceiver, filter);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		// display a notification about us starting. We put an icon in the status bar
		showNotification();
	}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		
		Log.i(ServerCommunicationService.class.getName(),"Received start id "+ startId+" : "+intent);
		checkNetworkAvailability();
		applicationState= true;
		if(mockLocationThread == null || !mockLocationThread.isAlive())
		{
			mockLocationThread = new Thread(mockLocationRunnable);
			mockLocationThread.start();
		}
		SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
		int userStatus = sharedPref.getInt(getString(R.string.user_status_pref), -1);
		
		boolean isUserLoggedIn = (userStatus == AgentState.STATUS_LOGGED_IN.getState() || userStatus == AgentState.STATUS_PENDING.getState());
		/* 
		 * if the user has performed the log IN , the service can start requesting updates to the location Manager about location changes , 
		 * otherwise it's useless to waist resources..and probably since a login will be performed , 
		 * the locationUpdates will be enabled by who is monitoring the login process
		 */
		if(isUserLoggedIn)
			requestPeriodicUpdate();
		
		/*
		 * we want this service to continue running until it is explicitly stopped, so return STICKY
		 */
		return START_STICKY;
	}
	
	@Override
	public boolean stopService(Intent name) {
		deliveryItemRepository.close();
		return super.stopService(name);
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		
		mNM.cancel(NOTIFICATION);
		mNM=null;
		synchronized (this) {
			applicationState=false;
		}
		
		//saqib: thread may be already killed by the OS
		if(mockLocationThread != null)
		{
			try {
				mockLocationThread.interrupt();
				mockLocationThread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		stopRequestingLocationUpdates();
		deliveryItemRepository.close();
		unregisterAll();

		locationManager=null;
		locationProviders=null;
		onDestinationValidationListener=null;
		deliveryItemRepository=null;
		unregisterReceiver(proximityIntentReceiver);
		
		// tell the user we stopped
		Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
	}
	
	
	/**
	 * This method will return the Remote control proxy interface to access the service functionalities 
	 * from other components
	 */
	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}
	@Override
	public boolean onUnbind(Intent intent) {
		return true;
	}
	
	
	// this is the object that receives interactions from clients.
	private final IBinder mBinder = new LocalBinder();
	
	
	/**
	 * Shows a notification while this service is runnig
	 */
	private void showNotification()
	{
		CharSequence text = getText(R.string.local_service_started);
		
		NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
		notificationBuilder.setSmallIcon(R.drawable.logo);
		notificationBuilder.setContentTitle(text);
		

		notificationBuilder.setWhen(System.currentTimeMillis());
		notificationBuilder.setContentText(getString(R.string.local_service_started));
		// the pending intent to launch our activity if the user selects this notification
		
		Intent contentIntent =new Intent(getApplicationContext(),NewTasksActivity.class);
		// The stack builder object will contain an artificial back stack for the
		// started Activity.
		// This ensures that navigating backward from the Activity leads out of
		// your application to the Home screen.
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(getApplicationContext());
		// Adds the back stack for the Intent (but not the Intent itself)
		stackBuilder.addParentStack(NewTasksActivity.class);
		stackBuilder.addNextIntent(contentIntent);
		PendingIntent resultPendingIntent =stackBuilder.getPendingIntent(0,PendingIntent.FLAG_CANCEL_CURRENT );
		notificationBuilder.setContentIntent(resultPendingIntent);
		mNM.notify(NOTIFICATION, notificationBuilder.build());
		// set the info for the views that show in the notification panel.	
	}
	
	/*************************************************************************************************/
	private LocationManager locationManager;
	private Location lastBestLocation;
	private ServerUpdateAsyncTask serverUpdateAsyncTask;
	private DeliveriesRepository deliveryItemRepository;
	private Thread mockLocationThread;
	private Runnable mockLocationRunnable;
	private List<LocationObserver> locationObservers;
	private OnDestinationValidationListener onDestinationValidationListener;
	private int locationProviderEnabled;
	private List<String>locationProviders;
	private volatile boolean applicationState;
	private static final int NEW_TASKS_NOTIFICATION_ID = 1;
	private static final int DELETED_TASKS_NOTIFICATION_ID = 2;
	private static final String MY_TEST_PROVIDER = "myTestProvider";
	
	private OnTasksListener onTasksListener;
	private boolean isProximityAlertSet = false;
	
	private int iOrder = 0;


	private volatile long proximitySignaledDeliveryItemId=-1;

	
	@Override
	public synchronized void onLocationChanged(Location location) {
		if(isBetterLocation(location, lastBestLocation))
			lastBestLocation=location;
		
		this.notiyAll();
		
		boolean bIsLastStatusUpdated = false;
		SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
		bIsLastStatusUpdated = pref.getBoolean(getString(R.string.delivery_status_updated), false);
		
		/*notify the server to update the changed position*/
		if(bIsLastStatusUpdated || !isProximityAlertSet)
		{
			/*if the proximityIntent is null , than we check the first one in the order*/
			List<DeliveryItem> items = deliveryItemRepository.findByQuery("_order="+iOrder);
			if(!items.isEmpty())
			{
				DeliveryItem deliveryItem = items.get(0);//FIXME: have to set properly get
				double latitude =  deliveryItem.getLatitutde();
				double longitude = deliveryItem.getLongitude();
				
				Bundle extras = new Bundle();
				
				extras.putLong(getString(R.string.delivery_id_notification_intent_param), deliveryItem.getId()) ;
			    this.proximitySignaledDeliveryItemId=deliveryItem.getId();
				addProximityAlert(latitude, longitude, extras);
				
				isProximityAlertSet = true;
				//iOrder++;
				
				Editor editor = pref.edit();
				editor.putBoolean(getString(R.string.delivery_status_updated), false);
				editor.commit();
			}
			
		}
		sendUpdateToServer(null);
        
	}
	
	private void addProximityAlert(double latitude, double longitude, Bundle bundle) 
	{
		float fRadius = 1000; // the radius of the central point of the alert region, in meters
		Integer iExpiration_Time = -1;// time for this proximity alert, in milliseconds, or -1 to indicate no expiration 
		
		Intent intent = new Intent();
		intent.setAction(getString(R.string.intent_action_proximity));
		intent.putExtras(bundle);
		
	    proximityIntent = PendingIntent.getBroadcast(getApplicationContext(), -1, intent, PendingIntent.FLAG_UPDATE_CURRENT);// will be used to generate an Intent to fire when entry to or exit from the alert region is detected
	    locationManager.addProximityAlert( latitude, longitude, fRadius, iExpiration_Time, proximityIntent );
	}

	@Override
	public void onProviderDisabled(String provider) {
		locationProviderEnabled &= locationProviders.indexOf(provider)<<0;
	}

	@Override
	public void onProviderEnabled(String provider) {
		
		locationProviderEnabled |= locationProviders.indexOf(provider)<<1;
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		
		if(status==2)
			locationProviderEnabled |= locationProviders.indexOf(provider)<<1;
		else
			locationProviderEnabled &= locationProviders.indexOf(provider)<<0;
		Log.d("", "provider status change :"+provider+" to "+ status);
		
	}
	
	private static final int TWO_MINUTES = 1000 * 60 * 2;
	public static final String NEW_TASK_NOTIFICATION_ACTION = "newTasks";
	public static final String DELETED_TASK_NOTIFICATION_ACTION = "deletedTasks";

	/** Determines whether one Location reading is better than the current Location fix
	  * @param location  The new Location that you want to evaluate
	  * @param currentBestLocation  The current Location fix, to which you want to compare the new one
	  */
	protected boolean isBetterLocation(Location location, Location currentBestLocation) {
	    if (currentBestLocation == null) {
	        // A new location is always better than no location
	        return true;
	    }

	    // Check whether the new location fix is newer or older
	    long timeDelta = location.getTime() - currentBestLocation.getTime();
	    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
	    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
	    boolean isNewer = timeDelta > 0;

	    // If it's been more than two minutes since the current location, use the new location
	    // because the user has likely moved
	    if (isSignificantlyNewer) {
	        return true;
	    // If the new location is more than two minutes older, it must be worse
	    } else if (isSignificantlyOlder) {
	        return false;
	    }

	    // Check whether the new location fix is more or less accurate
	    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
	    boolean isLessAccurate = accuracyDelta > 0;
	    boolean isMoreAccurate = accuracyDelta < 0;
	    boolean isSignificantlyLessAccurate = accuracyDelta > 200;

	    // Check if the old and new location are from the same provider
	    boolean isFromSameProvider = isSameProvider(location.getProvider(),
	            currentBestLocation.getProvider());

	    // Determine location quality using a combination of timeliness and accuracy
	    if (isMoreAccurate) {
	        return true;
	    } else if (isNewer && !isLessAccurate) {
	        return true;
	    } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
	        return true;
	    }
	    return false;
	}

	/** Checks whether two providers are the same */
	private boolean isSameProvider(String provider1, String provider2) {
	    if (provider1 == null) {
	      return provider2 == null;
	    }
	    return provider1.equals(provider2);
	}
	
	/**
     * As soon as we need to use some internet services , we have to check whether the user is allowed to surf the internet 
     * or it has to authenticate using higher level protocols.
     * The startegy is to try to contact a well known server and look at the response reader : if the respnse URL is different , probably meaning that a redirect happened in order to ask authentication information, 
     * we open a browser with such URL using an Intent 
     */
    private void checkNetworkAvailability()
	{
    	
    	ConnectivityManager conManager =  (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
    	//saqib: conManager becomes null if the wifi connection is no more available i.e. get disconnected
    	if(conManager != null && conManager.getActiveNetworkInfo() != null && !conManager.getActiveNetworkInfo().isConnected())
    	{
    		/* 
    		 * the network is not available and we should react in a proper way.
    		 * we Should start a loop in which we continue testing whether the network connection is present or not.
    		 */
    		//TODO
    	}
    	/* check authentication : check whether the authentication is performed at network level or 
    	 * there is the need to authenticate at application level using the browser
    	 */
    	AsyncTask<Void,Void,Boolean> m = new AsyncTask<Void, Void, Boolean>()
    			{
    				private String urlRedirect;
    				@Override
    				protected void onPostExecute(Boolean result) {
    					super.onPostExecute(result);
    					if(!result)
    					{
    						if(urlRedirect!=null)
    						{
	    						Intent i=new Intent();
								i.setAction(Intent.ACTION_VIEW);
								i.setData(Uri.parse(urlRedirect));
								startActivity(i);
    						}else
    						{
    							Toast.makeText(getApplicationContext(), getString(R.string.error_network_not_connected), Toast.LENGTH_SHORT).show();
    						}
    					}
    				}
					@Override
					protected Boolean doInBackground(Void... params) {
						HttpURLConnection urlConnection=null;
						try {
							URL url=new URL("http://www.google.com/");
							urlConnection =(HttpURLConnection) url.openConnection();
							if (!url.getHost().equals(urlConnection.getURL().getHost())) 
							{
								urlRedirect = urlConnection.getURL().getHost().toString();
								return false;
							}
							return true;
						} catch (Exception e) {
							e.printStackTrace();
						} finally {
							if(urlConnection!=null)
								urlConnection.disconnect();
						}
						return false;
					}
    			};
		m.execute();
	}

    @Override
    public void registerLocationObserver(LocationObserver observer) {
    	locationObservers.add(observer);
    	
    }
    @Override
    public void unregisterLocationObserver(LocationObserver observer) {
    	locationObservers.remove(observer);
    	
    }
    @Override
    public void unregisterAll() {
    	locationObservers.clear();
    	
    };
    
    @Override
    public void notiyAll() {
    	for(LocationObserver observer:locationObservers)
    		observer.updateLocation(lastBestLocation);
    }
    
    private void sendUpdateToServer(final List<DeliveryItem> destinations)
    {
    	/*
    	 * if the server is on going but the destinations are changed ( different from null ) we should retry after the current update
    	 */
    	if(destinations!=null && !destinations.isEmpty())
    	{
    		if(serverUpdateAsyncTask.getStatus()== AsyncTask.Status.RUNNING)
    		{
    			boolean cancel =  serverUpdateAsyncTask.cancel(true);
    			System.out.println("requested to shutdwon the task :"+cancel);
    			
    		}
    	}else if(!canStartServerUpdate())
    	{
    		System.out.println("pending task");
    		return;
    	}
        serverUpdateAsyncTask = new ServerUpdateAsyncTask(deliveryItemRepository,this){
    	
        	@Override
        	protected void onAfterAll(Boolean result) {
        		/*manageupdated Deliveries notification*/
        		if(hasUpdateDeliveries && result)
        		{
        			if(onDestinationValidationListener!=null)
						onDestinationValidationListener.onValidDestinations();
        		}else if(hasUpdateDeliveries && !result)
        		{
        			if(onDestinationValidationListener!=null)
						onDestinationValidationListener.onInvalidDestinations(resultMessage,errorMessage);
        		}
        		/*manage new Deliveries notification*/
        		if(hasNewDeliveries && result)
        		{
        			if(onTasksListener!=null)
        			{
        				onTasksListener.onNewTasksAvailable();
        			}
        		}else if(hasNewDeliveries && ! result)
        		{
        			Toast.makeText(ServerCommunicationService.this, getString(R.string.service_new_task_detected)+":\n"+errorMessage, Toast.LENGTH_SHORT).show();
        		}
        		
        		if(hasToDeleteDeliveries && result)
        		{
        			if(onTasksListener!=null)
        				onTasksListener.onDeletedTasks();
        		}
        		/*notify the location changed*/
        		ServerCommunicationService.this.notiyAll();
        	}

        	@Override
        	protected void deleteDeliveries(List<DeliveryItem> deliveries) {
        		int deletedCount = deliveryItemRepository.deleteAll(deliveries);
        		if(deletedCount!=deliveries.size())
        		{
        			// TODO notify error
        		}
        		
        		Bundle extra = new Bundle();
        		ArrayList<String> addressesForNotification = new ArrayList<String>();
        		for(DeliveryItem item : deliveries)
        		{
        			addressesForNotification.add(item.getDeliveryItemType().toString()+" : " +item.getAddress());
        		}
        		extra.putStringArrayList("itemsRemoved", addressesForNotification);
        		
        		sendNotification("Bike Pony items removal", 
						"The server removed "+deliveries.size()+" items", 
						deliveries.size()+"",
						true,
						R.drawable.logo,
						MainActivity.class, DELETED_TASK_NOTIFICATION_ACTION, 
						extra, 
						ServerCommunicationService.this, 
						DELETED_TASK_NOTIFICATION_ACTION,
						DELETED_TASKS_NOTIFICATION_ID);
        		
        	}
			@Override
			protected void updateDeliveries(List<DeliveryItem> deliveries) {
				deliveryItemRepository.updateAll(deliveries);
				List<DeliveryItem> items = deliveryItemRepository.findByQuery("_order="+iOrder);
				if(!items.isEmpty())
				{
					DeliveryItem firstItem = items.get(0);
					if(proximitySignaledDeliveryItemId!=-1 && firstItem.getId()!=proximitySignaledDeliveryItemId )
					{
						removeProximityAlert(getApplicationContext());				
						isProximityAlertSet=false;
					}
				}
				
				
			}

			@Override
			protected void addDeliveries(List<DeliveryItem> deliveries) {
				deliveryItemRepository.addAll(deliveries);
				
				Bundle extra = new Bundle();
    			for(DeliveryItem deliveryItem:deliveries)
    			{
    				String taskKey = "task_"+deliveryItem.getDeliveryId();
    				String[]addresses = extra.getStringArray(taskKey);
    				if(addresses==null)
    				{
    					addresses= new String[2];        					
    					extra.putStringArray(taskKey,addresses);
    				}
    				addresses[deliveryItem.getDeliveryItemType()==DeliveryItemType.DELIVERY?1:0]=deliveryItem.getAddress();
    			}
    			
    			sendNotification(deliveries.size()+" New BikePony Tasks", 
    							"You've received new tasks", 
    							deliveries.size()+"",
    							true,
    							R.drawable.logo,
    							NewTasksActivity.class, NEW_TASK_NOTIFICATION_ACTION, 
    							extra, 
    							ServerCommunicationService.this, 
    							NEW_TASK_NOTIFICATION_ACTION,
    							NEW_TASKS_NOTIFICATION_ID);
				
			}
        };
    
		String agentId = PreferenceManager.getDefaultSharedPreferences(this).getString(getString(R.string.userid_pref), "");
		ServerUpdateDataContainer dataContainer = new ServerUpdateDataContainer(destinations, agentId, lastBestLocation.getLatitude(), lastBestLocation.getLongitude());
    	serverUpdateAsyncTask.execute(dataContainer);
    }
    
    private void sendNotification(String contentTitle,String contentText,String contentInfo,boolean autoCancel,int smallIconRes,Class<?> activityClass,String intentAction,Bundle extras,Context context,String newTaskNotificationAction,int notificationId)
    {
    	NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(ServerCommunicationService.this);
		/*make the phone vibrate and play the default notification sound*/
		notificationBuilder.setDefaults(Notification.DEFAULT_VIBRATE| Notification.DEFAULT_SOUND);
		notificationBuilder.setContentTitle(contentTitle);
		notificationBuilder.setContentText(contentText);
		notificationBuilder.setContentInfo(contentInfo);
		notificationBuilder.setAutoCancel(autoCancel);
		notificationBuilder.setSmallIcon(smallIconRes);
		notificationBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
		
		Intent resultIntent = new Intent(context,activityClass);
		resultIntent.setAction(intentAction);
		
		resultIntent.putExtras(extras);
		resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
		stackBuilder.addParentStack(activityClass);
		stackBuilder.addNextIntent(resultIntent);
		
		//PendingIntent resultPendingIntent = PendingIntent.getActivity(ServerCommunicationService.this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);
		
		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
		notificationBuilder.setContentIntent(resultPendingIntent);
			// mId allows you to update the notification later on.
		mNM.notify(newTaskNotificationAction,notificationId, notificationBuilder.build());
		
    }
	private boolean canStartServerUpdate() {
		return serverUpdateAsyncTask==null || serverUpdateAsyncTask.getStatus()==AsyncTask.Status.FINISHED;
	}
    
    public OnDestinationValidationListener getOnDestinationValidationListener() {
		return onDestinationValidationListener;
	}
	public void setOnDestinationValidationListener(OnDestinationValidationListener onDestinationValidationListener) {
		this.onDestinationValidationListener = onDestinationValidationListener;
	}

	public interface OnDestinationValidationListener
    {
    	public void onValidDestinations();
    	public void onInvalidDestinations(ResultMessage resultMessage, String errorMessage);
    }
	public interface OnTasksListener
	{
		public void onNewTasksAvailable();
		public void onDeletedTasks();
	}
	
	private void setMockLocation(double latitude, double longitude){
		Location newLocation = new Location(MY_TEST_PROVIDER);
		newLocation.setLatitude(latitude);
		newLocation.setLongitude(longitude);
		newLocation.setTime(System.currentTimeMillis());
		newLocation.setAccuracy(150);
		Method locationJellyBeanFixMethod;
		try {
			locationJellyBeanFixMethod = Location.class.getMethod("makeComplete");
			if (locationJellyBeanFixMethod != null) {
				locationJellyBeanFixMethod.invoke(newLocation);
			}
		} catch (NoSuchMethodException e) {
			//ignore
		} catch (IllegalArgumentException e) {
			//ignore
		} catch (IllegalAccessException e) {
			//ignore
		} catch (InvocationTargetException e) {
			//ignore
		}
		locationManager.setTestProviderLocation(MY_TEST_PROVIDER, newLocation);
	} 
	
	public void startRequestingLocationUpdates()
	{
		requestPeriodicUpdate();
	}

	private void requestPeriodicUpdate() {
		locationManager.requestLocationUpdates(MY_TEST_PROVIDER, 6000, 0, this);
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 6000, 0, this);
		locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 6000, 0, this);
	}
	
	public void stopRequestingLocationUpdates()
	{
		locationManager.removeUpdates(this);
	}
	public void destinationsChanged(List<DeliveryItem> destinations)
    {
		if(lastBestLocation==null)
        {
        	Toast.makeText(this, "Update not possible:\nLocation service temporarily unavailable", Toast.LENGTH_SHORT).show();
        	return;
        }
    	sendUpdateToServer(destinations);
    }

	public OnTasksListener getOnTasksListener() {
		return onTasksListener;
	}

	public void setOnTasksListener(OnTasksListener onTasksListener) {
		this.onTasksListener = onTasksListener;
	}
	
	public synchronized void proximityReached(DeliveryItem item)
	{
		//removeProximityAlert(getApplicationContext());
		if(onProximityListener!=null)
			onProximityListener.onNearItemPoint(item);
	}
	public synchronized void proximityExited(DeliveryItem item)
	{
		if(onProximityListener!=null)
			onProximityListener.onFarFromItemPoint(item);
	}
	public OnProximityListener getOnProximityListener() {
		return onProximityListener;
	}

	public void setOnProximityListener(OnProximityListener onProximityListener) {
		this.onProximityListener = onProximityListener;
	}

	/**
	 * This class handles the proximity location pending Intent. It will receive
	 * message whenever the agent is near the first task point in its list ( either
	 * a pickup or a delivery point)
	 * 
	 * this Broadcast receiver receives and handles the Intent coming from the Service 
	 * and notifying the presence of new tasks available
	 * 
	 * @author nicola
	 * 
	 */
	public class ProximityIntentReceiver extends BroadcastReceiver
	{
		public static final int DELIVERY_POINT_NOTIFICATION_ID = 1000;
		
		public static final String DELIVERY_POINT_REACHED_TASK_NOTIFICATION_ACTION = "deliveryPointReached";
		
		@Override
		public void onReceive(Context context, Intent intent) {
			
			String k=LocationManager.KEY_PROXIMITY_ENTERING;
			boolean state= intent.getBooleanExtra(k, false);
			
			//Gives whether the user is entering or leaving in boolean form
			Bundle bundle = intent.getExtras();
			
			Long deliveryId = bundle.getLong(getString(R.string.delivery_id_notification_intent_param));
			List<DeliveryItem> deliveryItemList = deliveryItemRepository.findByQuery("_id="+deliveryId.toString());
			if(deliveryItemList != null && deliveryItemList.size() ==1)
			{
				boolean visible = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean(getString(R.string.notification_set_icon), false);
				
				if(state && !visible)
				{
					//int deliveryId =  Integer.valueOf(bundle.getString("delivery_id"));
					  
					String sMessage = ""; 
					proximityReached(deliveryItemList.get(0));
					removeProximityAlert(getApplicationContext());
					sendNotification(getString(R.string.notificaiton_on_notification_bar), sMessage, getString(R.string.notificaiton_on_notification_bar), true, R.drawable.logo, MainActivity.class, DELIVERY_POINT_REACHED_TASK_NOTIFICATION_ACTION, bundle, ServerCommunicationService.this, DELIVERY_POINT_REACHED_TASK_NOTIFICATION_ACTION, DELIVERY_POINT_NOTIFICATION_ID);
				}
				else
				{
					/*
					 * nicola : for the time being , the notification is not removed if the agent falls out the proximity region. We should decide which behaviour to use
					 * 
						removeNotification(DELIVERY_POINT_REACHED_TASK_NOTIFICATION_ACTION,DELIVERY_POINT_NOTIFICATION_ID);
						proximityExited(deliveryItemList.get(0));
						//removeProximityAlert(context);
					 */
				}
			}
			
		}
		public boolean isForeground(String myPackage, Context context)
		{
			ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
			List< ActivityManager.RunningTaskInfo > runningTaskInfo = manager.getRunningTasks(1); 

			ComponentName componentInfo = runningTaskInfo.get(0).topActivity;
			String sPackageName = componentInfo.getPackageName();
			if(sPackageName.equals(myPackage)) return true;
			return false;
		}
		

	}
	
	private synchronized void removeProximityAlert(Context context) 
	{
		PreferenceManager.getDefaultSharedPreferences(this).edit()
		.putBoolean("setNotificationIcon", false)/*.remove(getString(R.string.delivery_id_notification_intent_param))*/
		.commit();
		locationManager.removeProximityAlert(proximityIntent);
		proximitySignaledDeliveryItemId=-1;
	}
	
	public interface OnProximityListener
	{
		public void onNearItemPoint(DeliveryItem item);
		public void onFarFromItemPoint(DeliveryItem item);
	}

	public void removeNewTasksNotification()
	{
		mNM.cancel(NEW_TASK_NOTIFICATION_ACTION,NEW_TASKS_NOTIFICATION_ID);
	}

	public void removeDeletedTasksNotification() {
		mNM.cancel(DELETED_TASK_NOTIFICATION_ACTION,DELETED_TASKS_NOTIFICATION_ID);
	}

}
